using System;
using System.Collections.Generic;
using Tycho.Runtime;
using Tycho.Transactions;

namespace Tycho.Peyote {
    public class EvaluationDependencyEngine {
        private Dictionary<ITransactionalObject, HashSet<IDependencyNotificationListener>> Dependents;
        private Dictionary<IDependencyNotificationListener, HashSet<ITransactionalObject>> LastGlyphDependencies;
        private static ReferenceEqualityComparer<ITransactionalObject> TransactionalObjectComparer = new ReferenceEqualityComparer<ITransactionalObject> ();

        public EvaluationDependencyEngine () {
            Dependents = new Dictionary<ITransactionalObject, HashSet<IDependencyNotificationListener>> (TransactionalObjectComparer);
            LastGlyphDependencies = new Dictionary<IDependencyNotificationListener, HashSet<ITransactionalObject>> ();
        }

        private void RegisterDependency (IDependencyNotificationListener glyph, ITransactionalObject obj) {
            HashSet<IDependencyNotificationListener> deps;

            if (Dependents.TryGetValue (obj, out deps)) {
                deps.Add (glyph);
            } else {
                deps = new HashSet<IDependencyNotificationListener>();
                deps.Add (glyph);
                Dependents.Add (obj, deps);
            }
        }

        public void RegisterDependency (IDependencyNotificationListener glyph, IEnumerable<ITransactionalObject> objects) {
            HashSet<ITransactionalObject> objectSet = new HashSet<ITransactionalObject> (objects, TransactionalObjectComparer);
            HashSet<ITransactionalObject> lastObjects;
            
            if (LastGlyphDependencies.TryGetValue (glyph, out lastObjects)) {
                objectSet.ExceptWith (lastObjects);
                lastObjects.ExceptWith (objects);
                foreach (var obj in lastObjects) {
                    UnregisterDependency (glyph, obj);
                }
            }

            foreach (var obj in objectSet) {
                RegisterDependency (glyph, obj);
            }

            LastGlyphDependencies [glyph] = new HashSet<ITransactionalObject> (objects, TransactionalObjectComparer);
        }

        private void UnregisterDependency (IDependencyNotificationListener glyph, ITransactionalObject obj) {
            var deps = Dependents[obj];
            deps.Remove (glyph);

            if (deps.Count == 0) {
                Dependents.Remove (obj);
            }
        }

        private void NotifyDependents (IDependencyNotificationListener sourceOfNotification, ITransactionalObject obj, CircularReferenceVigil circularReferenceVigil) {
            HashSet<IDependencyNotificationListener> deps;
            if (Dependents.TryGetValue (obj, out deps)) {
                foreach (var dep in deps) {
                    if (dep != sourceOfNotification) {
                        if (!circularReferenceVigil.IsAlreadyNotified (sourceOfNotification, dep)) {
                            dep.NotifyModifiedDependency (circularReferenceVigil);
                            circularReferenceVigil.AddNotified (dep);
                        }
                    }
                }
            }
        }

        public void NotifyModification (IDependencyNotificationListener sourceOfNotification, IEnumerable<ITransactionalObject> objects, CircularReferenceVigil circularReferenceVigil) {
            if (circularReferenceVigil == null) {
                circularReferenceVigil = new CircularReferenceVigil ();
            }

            circularReferenceVigil.AddNotified (sourceOfNotification);

            foreach (var obj in objects) {
                NotifyDependents (sourceOfNotification, obj, circularReferenceVigil);
            }
        }
    }

    public class CircularReferenceVigil {
        private HashSet<IDependencyNotificationListener> AlreadyNotified;
        public List<CircularReference> CircularReferences { get; private set; }

        public CircularReferenceVigil() {
            AlreadyNotified = new HashSet<IDependencyNotificationListener>();
            CircularReferences = new List<CircularReference>();
        }

        public bool IsAlreadyNotified (IDependencyNotificationListener source, IDependencyNotificationListener dep) {
            if (AlreadyNotified.Contains (dep)) {
                CircularReferences.Add (new CircularReference (source, dep));
                return true;
            } else {
                return false;
            }
        }

        public void AddNotified (IDependencyNotificationListener notified) {
            AlreadyNotified.Add (notified);
        }
    }

    public class CircularReference {
        public IDependencyNotificationListener DepA { get; private set; }
        public IDependencyNotificationListener DepB { get; private set; }

        public CircularReference (IDependencyNotificationListener depA, IDependencyNotificationListener depB) {
            DepA = depA;
            DepB = depB;
        }
    }
}